/* 类型 */
export type ArithmeticOperation = '+' | '-' | '*' | '/'
export type KeyboardType = 'numberKey' | 'operationKey' | 'specialKey' | 'doneKey'
export interface KeyboardOnClickCallbackParams {
  mainNumString: string
  operationList: string[]
}
export interface CalculatorConstructorParams {
  keyboardOnClickCallback?: (arg0: KeyboardOnClickCallbackParams) => void
}

/* 按键分类 */
const numberKeys = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'CE', 'back', '.', '+/-'] // 数字符
const operationKeys = ['+', '-', '*', '/'] // 运算符
const specialKeys = ['%', '√', 'sqrt', 'x²', 'square', '1/x', 'reciprocal'] // 特殊符，数字已结算，但却没有新运算符；如果接下来按数字，则全部重置。如果按运算，则继续
const doneKeys = ['=', 'C'] // 终结符，一定会全部重置

/* 统一数字包装器（截取11位，不包含.和-） */
const handleNumberPack = (num: number | string, n = 11) => {
  const number = Number(num)
  if (isNaN(number)) return 0
  const str = number.toString()
  // 它最长的位数为11为，去除小数点
  let m = n
  if (str.indexOf('.') !== -1) {
    m += 1
  }
  if (str.indexOf('-') !== -1) {
    m += 1
  }
  return Number(str.slice(0, m))
}
/* 运算集 */
const handleOperation = {
  // 四则运算
  arithmetic: (num1: number, operation: ArithmeticOperation, num2: number) => {
    if (operation === '+') {
      return num1 + num2
    } else if (operation === '-') {
      return num1 - num2
    } else if (operation === '*') {
      return num1 * num2
    } else if (operation === '/') {
      return num1 / num2
    } else {
      return 0
    }
  },
  // 开平方根
  sqrt: (n: number) => {
    if (n >= 0) {
      return handleNumberPack(Math.sqrt(n))
    } else {
      throw '开平方根：无效。不可以为负数'
    }
  },
  // 平方
  square: (n: number) => handleNumberPack(n * n),
  // 倒数
  reciprocal: (n: number) => handleNumberPack(1 / n),
  // 百分比
  percentage: (n: number) => handleNumberPack(n / 100)
}
/* 更新operationList最后一个元素 */
const handleUpdateOperationListLastItem = (list: string[], item: string) => {
  const newList = [...list]
  const newItem = isNaN(Number(item)) ? item : Number(item).toString() // 如果item是数字形式，则进行处理，处理逻辑为：'-0.'应改为'0'
  newList[newList.length - 1] = newItem
  return newList
}

/* 计算器内核（模块化的），设计需求：
 * 1、每一次按键功能被执行，都进行标准输出。
 * 2、可以创建实例。
 * 3、内部方法不要暴露出去，目前是私有属性可以被看到，但不允许操作和覆盖。
 */
class Calculator {
  #mainNumString: string // 主屏number的展示（可为半成品）
  #operationList: string[] // 运算队列（用于纯展示）
  #lastKeyboardType: KeyboardType // 上一个按键的类型
  #lastOperation: ArithmeticOperation | null // 上一个运算符
  #lastNumber: number | null // 上一个值
  #lastResult: number | null // 上一个运算结果
  #keyboardOnClickCallback?: (arg0: KeyboardOnClickCallbackParams) => void // 点击键盘的回调函数

  constructor(params?: CalculatorConstructorParams) {
    const { keyboardOnClickCallback } = params || {}
    this.#mainNumString = '0'
    this.#operationList = ['0']
    this.#lastKeyboardType = 'doneKey'
    this.#lastOperation = null
    this.#lastNumber = null
    this.#lastResult = null
    this.#keyboardOnClickCallback = keyboardOnClickCallback
  }

  /* 设置点击键盘的回调函数（同，构造器中的功能） */
  setKeyboardOnClickCallback(fn?: (arg0: KeyboardOnClickCallbackParams) => void) {
    this.#keyboardOnClickCallback = fn
  }
  /* 对外标准输出内容 */
  getData() {
    // 输出标准内容
    const data: KeyboardOnClickCallbackParams = {
      mainNumString: this.#mainNumString,
      operationList: this.#operationList
    }
    return data
  }

  /* 对外所有键盘操作 */
  keyboardOnClick(key: string) {
    if (numberKeys.indexOf(key) !== -1) {
      /* 点击了数字键盘 */
      try {
        if (['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'].indexOf(key) !== -1) {
          this.#numberOnClick(key)
        } else if (key === '.') {
          this.#pointOnClick()
        } else if (key === 'CE') {
          this.#mainNumString = '0'
        } else if (key === 'back') {
          this.#mainNumString = this.#mainNumString.slice(0, -1) || '0'
        } else if (key === '+/-') {
          this.#mainNumString = (Number(this.#mainNumString) * -1).toString()
        }
        // 只有成功了，才会继续执行
        if (this.#lastKeyboardType === 'doneKey' || this.#lastKeyboardType === 'numberKey') {
          this.#operationList = handleUpdateOperationListLastItem(
            this.#operationList,
            this.#mainNumString
          ) // 更新当前数字
        } else if (this.#lastKeyboardType === 'operationKey') {
          this.#operationList = [...this.#operationList, this.#mainNumString] // 追加数字
        } else if (this.#lastKeyboardType === 'specialKey') {
          // 之前已结算，现在输入新数字，需要全部重置
          this.#operationList = [this.#mainNumString]
          this.#lastOperation = null
          this.#lastNumber = null
          this.#lastResult = null
        }
        this.#lastKeyboardType = 'numberKey'
      } catch (err) {
        console.log(err)
      }
    } else if (operationKeys.indexOf(key) !== -1) {
      /* 点击了运算键盘 */
      this.#operationOnClick(key as ArithmeticOperation)
      this.#lastKeyboardType = 'operationKey'
    } else if (specialKeys.indexOf(key) !== -1) {
      /* 点击了特殊符键盘 */
      try {
        const n = Number(this.#mainNumString)
        if (key === '%') {
          this.#mainNumString = handleOperation.percentage(n).toString()
        } else if (key === '√' || key === 'sqrt') {
          this.#mainNumString = handleOperation.sqrt(n).toString()
        } else if (key === 'x²' || key === 'square') {
          this.#mainNumString = handleOperation.square(n).toString()
        } else if (key === '1/x' || key === 'reciprocal') {
          this.#mainNumString = handleOperation.reciprocal(n).toString()
        }
        // 只有成功了，才会继续执行
        const str = key === '√' ? this.#mainNumString : `√(${n})`
        if (this.#lastKeyboardType === 'operationKey') {
          this.#operationList = [...this.#operationList, str] // 追加数字
        } else {
          this.#operationList = handleUpdateOperationListLastItem(this.#operationList, str) // 更新当前数字
        }
        this.#lastKeyboardType = 'specialKey'
      } catch (err) {
        console.log(err)
      }
    } else if (doneKeys.indexOf(key) !== -1) {
      /* 点击了终结符键盘 */
      try {
        if (key === '=') {
          this.#equalOnClick()
        } else if (key === 'C') {
          this.#mainNumString = '0'
        }
        // 只有成功了，才会继续执行
        this.#lastKeyboardType = 'doneKey'
        this.#operationList = [this.#mainNumString]
        this.#lastOperation = null
        this.#lastNumber = null
        this.#lastResult = null
      } catch (err) {
        console.log(err)
      }
    }

    this.#keyboardOnClickCallback?.(this.getData())
  }

  /* 私有属性 */
  /* 数字操作 */
  #numberOnClick(key: string) {
    if (this.#lastKeyboardType === 'numberKey') {
      // 追加现有数字
      this.#mainNumString = handleNumberPack(Number(this.#mainNumString + key)).toString()
    } else {
      // 全新的数字
      this.#mainNumString = key
    }
  }
  /* 点操作 */
  #pointOnClick() {
    if (this.#lastKeyboardType === 'numberKey') {
      // 追加现有数字
      this.#mainNumString = this.#mainNumString + '.'
    } else {
      // 全新的数字
      this.#mainNumString = '0.'
    }
  }
  /* 四则运算操作 */
  #operationOnClick(operation: ArithmeticOperation) {
    if (this.#lastKeyboardType === 'operationKey') {
      // 上一次点击了运算符，仅替换运算符
      this.#operationList = handleUpdateOperationListLastItem(this.#operationList, operation)
    } else {
      // 上一次点击了数字符|特殊符|终结符，走正常逻辑
      this.#lastNumber = Number(this.#mainNumString) // 保存上一个值
      if (this.#lastResult === null) {
        this.#lastResult = this.#lastNumber // 第一次进（相当于没有运算，直接赋给运算结果）
      } else if (this.#lastOperation) {
        const result = handleOperation.arithmetic(
          this.#lastResult,
          this.#lastOperation,
          this.#lastNumber
        )
        this.#lastResult = result // 保存上一个运算结果
        this.#mainNumString = result.toString() // 显示上一个运算结果
      } else {
        console.error('这里有bug一定要上报')
      }
      this.#operationList = [...this.#operationList, operation] // 追加运算队列
    }
    this.#lastOperation = operation // 记录本次的运算符
  }
  /* 等于操作 */
  #equalOnClick() {
    if (this.#lastResult === null || this.#lastOperation === null) {
      throw '等于操作：无效。lastResult或lastOperation不存在'
    } else {
      const result = handleOperation.arithmetic(
        this.#lastResult,
        this.#lastOperation,
        Number(this.#mainNumString)
      )
      this.#mainNumString = result.toString() // 显示运算结果
    }
  }
}
export default Calculator
